{% extends "templates/web.html" %}

{% block page_content %}
<style>
html, body { overflow: hidden; }
.oms-container {
    width: 100%;
    min-height: 100vh;
    background: #fff;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0;
    overflow-x: hidden;
}
.oms-toolbar {
    display: flex;
    gap: 12px;
    padding: 14px 16px;
    border-bottom: 1px solid #e9ecef;
    align-items: center;
    background: #fff;
    z-index: 1;
}
.oms-toolbar input, .oms-toolbar select {
    height: 32px;
    padding: 0 10px;
    border: 1px solid #ced4da;
    border-radius: 4px;
    font-size: 13px;
}
.oms-toolbar .btn {
    height: 32px;
    padding: 0 14px;
    border: 1px solid #ced4da;
    border-radius: 4px;
    background: #fff;
    cursor: pointer;
}
.oms-toolbar .btn.primary { background: #1989fa; color: #fff; border-color: #1989fa; }
.oms-table-wrap {
    flex: 1;
    overflow-x: auto;
    overflow-y: auto;
    background: #fff;
    max-width: 100%;
}
table.oms-table {
    width: max-content;
    min-width: 100%;
    border-collapse: separate;
    table-layout: auto;
}
table.oms-table thead th {
    background: #f6f7fb;
    color: #333;
    font-weight: 600;
    padding: 10px 8px;
    border-bottom: 1px solid #e9ecef;
    text-align: left;
    font-size: 13px;
}
table.oms-table tbody td {
    border-bottom: 1px solid #f1f3f5;
    padding: 10px 8px;
    font-size: 13px;
    white-space: normal;
    word-break: break-word;
}
table.oms-table tbody tr:hover {
    background: #f8f9fa;
}
.muted { color: #6c757d; font-size: 12px; }
.status-badge { padding: 2px 8px; border-radius: 10px; background: #eef2ff; color: #3b5bdb; font-size: 12px; }
.link { color: #0d6efd; cursor: pointer; }
.copyable { cursor: pointer; color:#0d6efd; }
.copyable:hover { text-decoration: underline; }
/* 限制单元格最多3行展示 */
.cell{display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;white-space:normal;word-break:break-word;}
/* 固定左侧列（到状态列） */
:root{
  --w-check:28px;
  --w-waybill:160px;
  --w-customer:180px;
  --w-tracking:200px;
  --w-product:140px;
  --w-dest:120px;
  --w-receiver:160px;
  --w-phone:140px;
  --w-status:100px;
}
.sticky-col{position:sticky;z-index:2;background:#fff;}
.fix-col-1{left:0;width:var(--w-check)}
.fix-col-2{left:calc(var(--w-check));width:var(--w-waybill)}
.fix-col-3{left:calc(var(--w-check) + var(--w-waybill));width:var(--w-customer)}
.fix-col-4{left:calc(var(--w-check) + var(--w-waybill) + var(--w-customer));width:var(--w-tracking)}
.fix-col-5{left:calc(var(--w-check) + var(--w-waybill) + var(--w-customer) + var(--w-tracking));width:var(--w-product)}
.fix-col-6{left:calc(var(--w-check) + var(--w-waybill) + var(--w-customer) + var(--w-tracking) + var(--w-product));width:var(--w-dest)}
.fix-col-7{left:calc(var(--w-check) + var(--w-waybill) + var(--w-customer) + var(--w-tracking) + var(--w-product) + var(--w-dest));width:var(--w-receiver)}
.fix-col-8{left:calc(var(--w-check) + var(--w-waybill) + var(--w-customer) + var(--w-tracking) + var(--w-product) + var(--w-dest) + var(--w-receiver));width:var(--w-phone)}
.fix-col-9{left:calc(var(--w-check) + var(--w-waybill) + var(--w-customer) + var(--w-tracking) + var(--w-product) + var(--w-dest) + var(--w-receiver) + var(--w-phone));width:var(--w-status)}
.oms-table thead .sticky-col{z-index:3;}
</style>

<div class="oms-container">
  <div class="oms-toolbar">
    <input id="kw" type="text" data-i18n-placeholder="支持模糊搜索当前页面所有内容" placeholder="支持模糊搜索当前页面所有内容" style="min-width:280px;">
    <div style="display:flex;gap:8px;align-items:center;">
      <span class="muted" data-i18n="创建时间">创建时间</span>
      <input id="from" type="datetime-local">
      <span class="muted">→</span>
      <input id="to" type="datetime-local">
    </div>
    <div style="display:flex;gap:8px;align-items:center;">
      <span class="muted" data-i18n="订单筛选">订单筛选</span>
      <select id="statusFilter">
        <option value="all" data-i18n="全部订单">全部订单</option>
        <option value="exceptions" data-i18n="异常订单">异常订单</option>
        <option value="in_transit" data-i18n="运送中订单">运送中订单</option>
        <option value="delivered" data-i18n="已送达订单">已送达订单</option>
      </select>
    </div>
    <button class="btn primary" id="btnSearch" data-i18n="查询">查询</button>
    <div style="flex:1;"></div>
    <div style="display:flex;gap:8px;">
      <button class="btn" id="btnCreateIssue" data-i18n="创建问题">创建问题</button>
      <button class="btn" id="btnHold" data-i18n="拦截">拦截</button>
      <button class="btn" id="btnExport" data-i18n="导出">导出</button>
    </div>
  </div>

  <div class="oms-table-wrap">
    <table class="oms-table">
      <thead>
        <tr>
          <th class="sticky-col fix-col-1" style="width:20px;"></th>
          <th class="sticky-col fix-col-2"><div class="cell" data-i18n="运单号">运单号</div></th>
          <th class="sticky-col fix-col-3"><div class="cell" data-i18n="客户单号">客户单号</div></th>
          <th class="sticky-col fix-col-4"><div class="cell" data-i18n="跟踪号">跟踪号</div></th>
          <th class="sticky-col fix-col-5"><div class="cell" data-i18n="运输方式">运输方式</div></th>
          <th class="sticky-col fix-col-6"><div class="cell" data-i18n="收件人国家">收件人国家</div></th>
          <th class="sticky-col fix-col-7"><div class="cell" data-i18n="收件人姓名">收件人姓名</div></th>
          <th class="sticky-col fix-col-8"><div class="cell" data-i18n="电话">电话</div></th>
          <th class="sticky-col fix-col-9"><div class="cell" data-i18n="状态">状态</div></th>          
      <th data-i18n="收件人州省">收件人州省</th>
      <th data-i18n="收件人地址">收件人地址</th>
      <th data-i18n="订单状态">订单状态</th>
      <th data-i18n="包裹重量">包裹重量</th>
      <th data-i18n="计费重">计费重</th>
      <th data-i18n="外包裹数量">外包裹数量</th>
      <th data-i18n="创建时间">创建时间</th>
      <th data-i18n="收货时间">收货时间</th>
      <th data-i18n="发货时间">发货时间</th>
      <th data-i18n="签收时间">签收时间</th>
      <th data-i18n="最新事件">最新事件</th>
          <th data-i18n="签收证明">签收证明</th>
          <th data-i18n="关联的问题">关联的问题</th>
        </tr>
      </thead>
      <tbody id="omsTBody"></tbody>
    </table>
    <div id="omsPager" style="display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:10px 12px;border-top:1px solid #e9ecef;">
      <span class="muted">每页</span>
      <select id="pageSize" style="height:28px;padding:0 6px;">
        <option value="20">20</option>
        <option value="50">50</option>
        <option value="100">100</option>
      </select>
      <button class="btn" id="prevPage">上一页</button>
      <span class="muted" id="pageInfo">1 / 1</span>
      <button class="btn" id="nextPage">下一页</button>
    </div>
  </div>
</div>

<script>
let omsRows = [];
let rendered = [];
let pageIndex = 1;
let pageSize = 20;
let serverTotal = 0;
let statusFilter = 'all'; // all | exceptions | in_transit | delivered
// 轨迹节点映射：中文/英文/分类
const TRACK_NODE_DICT = {
  CUSTOMS_EXCEPTION: { zh: '清关异常', en: 'Abnormal customs clearance', cat: '异常' },
  PRE_ADVICING: { zh: '服务商电子预报信息', en: 'Pre-alert', cat: '转运中' },
  ARRIVE_CONFIRM_OC: { zh: '到达代理/口岸/港口', en: 'Arrived at dedicated agent/origin port', cat: '转运中' },
  DEPART_CONFIRM_OC: { zh: '发货', en: 'Left origin hub', cat: '转运中' },
  RETURN_IN_WAREHOUSE: { zh: '退件在仓(国内)', en: 'Return in warehouse', cat: '转运中' },
  MAIN_LINE_DEPART: { zh: '离港/发车/发车', en: 'Flight/Truck/Train departure', cat: '转运中' },
  MAIN_LINE_ARRIVE: { zh: '到港/到达', en: 'Flight/Truck/Train arrival', cat: '转运中' },
  CUSTOMS_COMPLETE: { zh: '清关完成', en: 'Clearance processing completed', cat: '转运中' },
  CUSTOMS_RELEASE: { zh: '海关放行', en: 'Customs released', cat: '转运中' },
  TRANSIT_OUT: { zh: '调拨出仓', en: 'Transit out', cat: '转运中' },
  TRANSIT_IN: { zh: '调拨入仓', en: 'Transit in', cat: '转运中' },
  FIRST_MILE_ARRIVE: { zh: '签入', en: 'Arrival scan', cat: '转运中' },
  FIRST_MILE_DEPART: { zh: '签出', en: 'Departure scan', cat: '转运中' },
  IN_TRANSIT: { zh: '转运中', en: 'In transit', cat: '转运中' },
  CUSTOMS_PROCESSING: { zh: '清关中', en: 'Clearance processing', cat: '转运中' },
  EXPORT_CUSTOMS_COMPLETE: { zh: '出口报关完成', en: 'EXPORT_CUSTOMS_COMPLETE', cat: '转运中' },
  PRE_INFO: { zh: '预上网', en: 'Pre Ascan', cat: '转运中' },
  PICKUP_CARGO_TERMINAL: { zh: '货站提货', en: 'Collected at cargo terminal', cat: '转运中' },
  OTHER: { zh: '其他', en: 'Other', cat: '转运中' },
  TRANSITHUB_ARRIVE: { zh: '交货给服务商/干线/末端', en: 'Delivery to local carrier', cat: '转运中' },
  ARRIVE_ORIN_CUSTOMS: { zh: '报关中', en: 'Clearance processing', cat: '转运中' },
  IN_TRANSIT_CARRIER: { zh: '服务商转运中', en: 'In transit by carrier', cat: '运输中' },
  DELIVERY_ATTEMPT: { zh: '派送中/尝试派送/预计派送', en: 'Out for delivery', cat: '运输中' },
  CARRIER_PICKUP: { zh: '服务商收货/提取', en: 'Collected by carrier', cat: '运输中' },
  CUSTOMS_INSPCTION: { zh: '海关查验中', en: 'Under customs inspection', cat: '异常' },
  AIRPORT_INSPECTION: { zh: '出口机场安检查验', en: 'Export airport security inspection', cat: '异常' },
  PACKAGE_EXCEPTION: { zh: '包裹异常/弃件/销毁/理赔/轨迹断更', en: 'Exception', cat: '异常' },
  CUSTOMS_HOLD: { zh: '海关查没', en: 'Customs detention', cat: '异常' },
  PACKAGE_LOST: { zh: '确认丢件', en: 'Parcel lost', cat: '异常' },
  CUSTOMS_DELAY: { zh: '清关延误', en: 'Customs clearance delay', cat: '异常' },
  AIRPORT_HOLD: { zh: '出口机场安检没收', en: 'Export airport security hold', cat: '异常' },
  DELIVERY_FAILURE: { zh: '派送失败/自提失败', en: 'Delivery failed', cat: '异常' },
  DELIVERED: { zh: '妥投', en: 'Delivered', cat: '妥投' },
  RETURNED: { zh: '退件', en: 'Returned', cat: '退件' },
  RETURNED_TO_SENDER: { zh: '退件出仓(国内)', en: 'Warehouse out and return', cat: '退件' },
  RETURNED_BACK: { zh: '服务商退回中', en: 'Returned by carrier', cat: '退件' },
};
function getNodeMeta(code){
  const k = String(code || '').toUpperCase();
  return TRACK_NODE_DICT[k] || { zh: '', en: '', cat: '' };
}
const _qs = (function(){ try { return new URLSearchParams(window.location.search); } catch(e){ return { get: () => null }; } })();
const MODE = (_qs.get('mode') || '').toLowerCase();

document.addEventListener('DOMContentLoaded', () => {
  // 页面级 i18n 应用
  (function applyI18n(){
    try{
      function getLang(){
        try{
          const sel = document.getElementById('flowa-lang-select');
          return (sel && sel.value) || (window.frappe && frappe.boot && (frappe.boot.lang || (frappe.boot.user && frappe.boot.user.language))) || 'zh';
        }catch(e){ return 'zh'; }
      }
      function t(key){
        try{
          const dict = window.flowaTranslations || {};
          const entry = dict[key];
          if(!entry) return key;
          if(typeof entry === 'string') return entry;
          const low = String(getLang()||'zh').toLowerCase();
          if(low.indexOf('zh')===0) return entry.zh || entry.en || key;
          return entry.en || key;
        }catch(e){ return key; }
      }
      document.querySelectorAll('[data-i18n]').forEach(el=>{
        const key = el.getAttribute('data-i18n');
        el.textContent = t(key);
      });
      document.querySelectorAll('[data-i18n-placeholder]').forEach(el=>{
        const key = el.getAttribute('data-i18n-placeholder');
        el.setAttribute('placeholder', t(key));
      });
    }catch(e){}
  })();
  document.getElementById('btnSearch').addEventListener('click', refresh);
  document.getElementById('btnExport').addEventListener('click', exportCurrent);
  document.getElementById('btnCreateIssue').addEventListener('click', handleCreateIssueClick);
  document.getElementById('btnHold').addEventListener('click', handleHoldClick);
  // 订单状态筛选下拉：根据 URL mode 初始化，并监听变化
  const statusSelect = document.getElementById('statusFilter');
  if (statusSelect) {
    if (MODE === 'exceptions' || MODE === 'in_transit' || MODE === 'delivered') {
      statusFilter = MODE;
    } else {
      statusFilter = 'all';
    }
    statusSelect.value = statusFilter;
    statusSelect.addEventListener('change', () => {
      statusFilter = statusSelect.value || 'all';
      pageIndex = 1;
      refresh();
    });
  }
  document.getElementById('prevPage').addEventListener('click', () => { if (pageIndex > 1) { pageIndex--; refresh(); }});
  document.getElementById('nextPage').addEventListener('click', () => { const max = Math.max(1, Math.ceil((serverTotal || 0) / pageSize)); if (pageIndex < max) { pageIndex++; refresh(); }});
  document.getElementById('pageSize').addEventListener('change', (e) => { pageSize = parseInt(e.target.value || '20', 10); if (isNaN(pageSize) || pageSize <= 0) pageSize = 20; pageIndex = 1; refresh(); });
  // 复制事件代理
  document.getElementById('omsTBody').addEventListener('click', (e) => {
    const t = e.target;
    if (!t) return;

    // 运单号：点击打开云途详情，而不是复制
    if (t.matches('.copyable[data-field="waybill"]')) {
      const val = t.getAttribute('data-copy') || '';
      if (val) {
        // 直接复用原来的 handleDetailClick 逻辑，但传入当前运单号
        openYunDetailFor(val);
      }
      return;
    }

    // 其他可复制字段
    if (t.classList && t.classList.contains('copyable')) {
      const val = t.getAttribute('data-copy') || '';
      if (val) {
        navigator.clipboard && navigator.clipboard.writeText ? navigator.clipboard.writeText(val) : document.execCommand && document.execCommand('copy');
        frappe && frappe.msgprint ? frappe.msgprint('已复制: ' + val) : alert('已复制: ' + val);
      }
    }

    // 点击 Issue 链接，打开问题编辑器
    if (t.dataset && t.dataset.issue) {
      openIssueEditor(t.dataset.issue);
    }
  });
  refresh();
});

async function refresh() {
  const kw = (document.getElementById('kw').value || '').trim().toLowerCase();
  const resp = await frappe.call({
    method: 'upsystem.upsystem.api_tms_tracking.get_shop_shipments',
    args: {
      page: pageIndex,
      page_size: pageSize,
    },
  });
  const msg = resp.message || {};
  const shipments = msg.shipments || [];
  serverTotal = msg.total || 0;
  // 将 Logistics Order Inst 映射为表格基础行（尽量一一对应 LOS 字段）
  omsRows = shipments.map(s => ({
    // 运单号：LOS.waybill_number，若无则用 tracking_number 兜底
    deliveryOrderNo: s.waybill_number || s.tracking_number || s.los_name || '',
    // 客户单号 / ERP 订单：Sales Order 名称
    customerOrderNo: s.sales_order || '',
    erpOrder: s.sales_order || '',
    // 物流产品
    productName: s.yt_product_code || '',
    // 目的国
    dest: s.destination_country || '',
    // 跟踪号
    trackingNumber: s.tracking_number || '',
    // 收件人信息（直接来自 LOS）
    receiver_name: s.recipient_name || '',
    receiver_phone: s.recipient_phone || '',
    receiver_state: s.recipient_state || '',
    receiver_address: s.recipient_address || '',
    receiver_country: s.recipient_country || '',
    // 件数 / 重量信息
    pieces: s.pieces || '',
    weight_los: s.actual_weight || '',
    charge_weight_los: s.charge_weight || '',
    warehouse_weight: s.warehouse_weight || '',
    // 时间维度（LOS 已经由定时任务写入）
    creationTime_los: s.creation_time || '',
    checkInTime_los: s.time_of_receipt || '',
    deliveredTime_los: s.delivery_time || '',
    cancelTime_los: s.cancel_time || '',
    returnTime_los: s.return_time || '',
    pod_url_los: s.pod_url || '',
    // 轨迹与状态
    logistics_status_code: s.logistics_status_code || '',
    track_node_code: s.track_node_code || '',
    last_event_text_los: s.last_event_text || '',
    last_event_time_los: s.last_event_time || '',
    track_last_updated_los: s.track_last_updated || '',
    track_history_json: s.track_history_json || '',
    // Issue 相关
    issue_name: s.issue || '',
    issue_status_los: s.issue_status || '',
    issue_raised_at_los: s.issue_raised_at || '',
  }));
  // 批量查轨迹以拿到时间与最新事件
  const keys = [...new Set(omsRows.map(r => r.deliveryOrderNo).filter(Boolean))];
  let trackMap = {};
  if (keys.length) {
    const tr = await frappe.call({ method: 'upsystem.upsystem.api.batch_tracking', args: { tracking_numbers: keys } });
    (tr.message && tr.message.data || []).forEach(it => {
      if (!it || !it.success) return;
      trackMap[it.tracking_number] = it;
    });
  }
  rendered = omsRows.map((r, i) => {
    const t = trackMap[r.deliveryOrderNo] || null;
    const data = (t && t.data) || {};
    const hist = (data.tracking_history || []).slice();
    // 最新/最早时间（按时间戳比较，保证取到真正最新）
    let latest = null;
    let earliest = null;
    hist.forEach(ev => {
      const ts = ev.timestamp || ev.process_time;
      if (ts) {
        if (!earliest || new Date(ts) < new Date(earliest.timestamp || earliest.process_time)) earliest = ev;
        if (!latest || new Date(ts) > new Date(latest.timestamp || latest.process_time)) latest = ev;
      }
    });
    // 签收时间
    let delivered = null;
    const deliveredHit = hist.find(ev => {
      const code = (ev.status || ev.track_node_code || '').toString().toUpperCase();
      const desc = (ev.description || ev.event || '').toString().toLowerCase();
      return code === 'DELIVERED' || desc.includes('delivered') || desc.includes('签收');
    });
    if (deliveredHit) delivered = deliveredHit.timestamp;

    return {
      ...r,
      // 轨迹信息：优先使用云途实时数据，其次回退到 LOS 字段
      waybill_number: data.waybill_number || r.deliveryOrderNo || '',
      weight: data.weight || r.weight_los || r.warehouse_weight || '',
      charge_weight: r.charge_weight_los || '',
      outer_pkg_count: r.pieces || '',
      proof_url: r.pod_url_los || (data.proof_url || ''),
      creationTime: earliest ? earliest.timestamp : (r.creationTime_los || data.check_in_time || ''),
      checkInTime: r.checkInTime_los || data.check_in_time || '',
      checkOutTime: data.check_out_time || '',
      deliveredTime: r.deliveredTime_los || delivered || '',
      latestTime: latest ? latest.timestamp : (r.last_event_time_los || ''),
      latestEvent: latest ? (latest.description || latest.event || '') : (r.last_event_text_los || data.last_event || ''),
      // 状态：云途状态/包裹状态优先，退化到 LOS 状态码
      statusText: (data.status || data.package_status || r.logistics_status_code || 'processing'),
    };
  }).filter(row => {
    if (!kw) return true;
    return Object.values(row).some(v => (String(v || '')).toLowerCase().includes(kw));
  });

  // 后续根据订单状态筛选（异常 / 运输中 / 已送达）或加载 Issue 信息
  const orderNames = rendered.map(r => r.erpOrder).filter(Boolean);
  if (orderNames.length) {
    if (statusFilter === 'exceptions' || statusFilter === 'in_transit' || statusFilter === 'delivered') {
      // 若仅查看某类物流状态（异常 / 运输中 / 已送达），则按 Logistics Order Inst 规则过滤
      try {
        const res = await frappe.call({
          method: 'upsystem.upsystem.api_traking.get_logistics_orders_by_status',
          args: {
            order_names: JSON.stringify(orderNames),
            mode: statusFilter || 'exceptions',
          },
        });
        const orders = (res.message && res.message.orders) || [];
        const exSet = new Set(orders);
        rendered = rendered.filter(r => exSet.has(r.erpOrder));
      } catch (e) {
        console.warn('load logistics orders by status failed', e);
      }
    } else {
      // 普通模式下，加载关联 Issue 信息
      try {
        const res = await frappe.call({
          method: 'upsystem.upsystem.api_traking.get_issues_by_orders',
          args: { order_names: JSON.stringify(orderNames) },
        });
        const map = (res.message && res.message.data) || {};
        rendered = rendered.map(r => ({ ...r, issues: map[r.erpOrder] || [] }));
      } catch (e) {
        console.warn('load issues failed', e);
      }
    }
  }

  renderTable();
  // 不再自动拉取订单详情，由用户点击“详情”时按需加载
}

function renderTable() {
  const tbody = document.getElementById('omsTBody');
  tbody.innerHTML = '';
  const pageData = rendered;
  pageData.forEach((r, idx) => {
    const tr = document.createElement('tr');
    const issuesHtml = (r.issues || []).map(it => {
      const when = it.creation ? fmt(it.creation) : '';
      const status = it.status || '';
      return `<a href="javascript:void(0)" data-issue="${it.name}" title="${status} ${when}">${it.name}</a> <span class="muted">(${status} ${when})</span>`;
    }).join('<br>');
    tr.innerHTML = `
      <td class="sticky-col fix-col-1"><input type="checkbox" data-index="${idx}"></td>
      <td class="sticky-col fix-col-2"><div class="cell"><span class="copyable" data-field="waybill" data-copy="${r.deliveryOrderNo || ''}" title="点击查看云途详情">${r.deliveryOrderNo || '-'}</span></div></td>
      <td class="sticky-col fix-col-3"><div class="cell">${r.customerOrderNo || '-'}</div></td>
      <td class="sticky-col fix-col-4"><div class="cell"><span class="copyable" data-copy="${r.trackingNumber || ''}" title="点击复制">${r.trackingNumber || '-'}</span></div></td>
      <td class="sticky-col fix-col-5"><div class="cell">${r.productName || '-'}</div></td>
      <td class="sticky-col fix-col-6"><div class="cell">${r.dest || '-'}</div></td>
      <td class="sticky-col fix-col-7"><div class="cell">${r.receiver_name || '-'}</div></td>
      <td class="sticky-col fix-col-8"><div class="cell">${r.receiver_phone || '-'}</div></td>
      <td class="sticky-col fix-col-9"><div class="cell"><span class="status-badge">${mapStatus(r.statusText)}</span></div></td>
      <td><div class="cell">${r.receiver_state || '-'}</div></td>
      <td><div class="cell">${r.receiver_address || '-'}</div></td>
      <td><div class="cell">${(r.logistics_status_code || '').toString().toUpperCase() || '-'}</div></td>
      <td><div class="cell">${r.weight || '-'}</div></td>
      <td><div class="cell">${r.charge_weight || '-'}</div></td>
      <td>${r.outer_pkg_count || '-'}</td>
      <td>${fmt(r.creationTime)}</td>
      <td>${fmt(r.checkInTime)}</td>
      <td>${fmt(r.checkOutTime)}</td>
      <td>${fmt(r.deliveredTime)}</td>
      <td>
        <div class="cell">
          <div class="muted">${fmt(r.latestTime)}</div>
          <div>${r.latestEvent || '-'}</div>
        </div>
      </td>
      <td><div class="cell">${r.proof_url ? `<a href="${r.proof_url}" target="_blank">查看</a>` : '-'}</div></td>
      <td><div class="cell">${issuesHtml || '-'}</div></td>
    `;
    tbody.appendChild(tr);
  });
  const max = Math.max(1, Math.ceil((serverTotal || 0) / pageSize));
  document.getElementById('pageInfo').textContent = `${pageIndex} / ${max}`;
}

function fmt(ts) {
  if (!ts) return '-';
  const d = new Date(ts);
  if (isNaN(d.getTime())) return ts;
  return d.toLocaleString('zh-CN', { hour12:false });
}

function mapStatus(s) {
  const x = String(s || '').toLowerCase();
  if (x.includes('delivered')) return '已签收';
  if (x.includes('return')) return '已退回';
  if (x.includes('alert') || x.includes('exception')) return '异常';
  if (x.includes('transit')) return '运输中';
  return '处理中';
}

async function openDetail(idx) {
  const row = rendered[idx];
  if (!row) return;
  // 并发：订单详情 + 单票轨迹（用于时间线）
  const [od, td] = await Promise.allSettled([
    frappe.call({ method: 'upsystem.upsystem.api.get_order_detail', args: { query_data: { order_number: row.deliveryOrderNo, waybill_number: row.deliveryOrderNo } } }),
    frappe.call({ method: 'upsystem.upsystem.api.batch_tracking', args: { tracking_numbers: [row.deliveryOrderNo] } })
  ]);
  const detail = (od.status === 'fulfilled' && od.value && od.value.message && od.value.message.data) || {};
  const trackResp = (td.status === 'fulfilled' && td.value && td.value.message && td.value.message.data) || [];
  const trackData = trackResp.length && trackResp[0] && trackResp[0].data ? trackResp[0].data : {};
  showDetailDrawer(row, detail, trackData);
}

// 根据运单号打开云途详情抽屉（用于点击运单号超链接）
async function openYunDetailFor(orderNumber) {
  if (!orderNumber) return;
  // 在当前 rendered 中找到第一条匹配的行，主要用于展示本地信息
  const idx = rendered.findIndex(r => (r.deliveryOrderNo || '') === orderNumber);
  const row = idx >= 0 ? rendered[idx] : { deliveryOrderNo: orderNumber };
  const [od, td] = await Promise.allSettled([
    frappe.call({ method: 'upsystem.upsystem.api.get_order_detail', args: { query_data: { order_number: orderNumber, waybill_number: orderNumber } } }),
    frappe.call({ method: 'upsystem.upsystem.api.batch_tracking', args: { tracking_numbers: [orderNumber] } })
  ]);
  const detail = (od.status === 'fulfilled' && od.value && od.value.message && od.value.message.data) || {};
  const trackResp = (td.status === 'fulfilled' && td.value && td.value.message && td.value.message.data) || [];
  const trackData = trackResp.length && trackResp[0] && trackResp[0].data ? trackResp[0].data : {};
  showDetailDrawer(row, detail, trackData);
}

async function handleCreateIssueClick() {
  const checked = Array.from(document.querySelectorAll('#omsTBody input[type="checkbox"]:checked'));
  if (checked.length === 0) { frappe.msgprint ? frappe.msgprint('请先选择一条订单') : alert('请先选择一条订单'); return; }
  if (checked.length > 1) { frappe.msgprint ? frappe.msgprint('一次仅支持单条创建问题，请只勾选一条') : alert('一次仅支持单条创建问题，请只勾选一条'); return; }
  const idx = parseInt(checked[0].dataset.index || '-1', 10);
  if (isNaN(idx) || idx < 0) { frappe.msgprint ? frappe.msgprint('无法定位所选订单，请刷新后重试') : alert('无法定位所选订单，请刷新后重试'); return; }
  const row = rendered[idx];
  // 拉详情与轨迹，拼接文本
  const [od, td] = await Promise.allSettled([
    frappe.call({ method: 'upsystem.upsystem.api.get_order_detail', args: { query_data: { order_number: row.deliveryOrderNo, waybill_number: row.deliveryOrderNo } } }),
    frappe.call({ method: 'upsystem.upsystem.api.batch_tracking', args: { tracking_numbers: [row.deliveryOrderNo] } })
  ]);
  const detail = (od.status === 'fulfilled' && od.value && od.value.message && od.value.message.data) || {};
  const trackResp = (td.status === 'fulfilled' && td.value && td.value.message && td.value.message.data) || [];
  const trackData = trackResp.length && trackResp[0] && trackResp[0].data ? trackResp[0].data : {};
  openIssueCreator(row, detail, trackData);
}

function showIssueDialog(content) {
  // 自定义轻量弹窗，避免依赖 frappe.ui.form.make_control
  const overlay = document.createElement('div');
  overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:1200;display:flex;align-items:center;justify-content:center;';
  const panel = document.createElement('div');
  panel.style.cssText = 'width:820px;max-width:90vw;background:#fff;border-radius:8px;box-shadow:0 10px 30px rgba(0,0,0,.2);display:flex;flex-direction:column;';
  panel.innerHTML = `
    <div style="padding:10px 12px;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center;">
      <div style="font-weight:600;">创建问题备注</div>
      <span style="cursor:pointer;color:#666;" id="issueDlgClose">关闭</span>
    </div>
    <div style="padding:12px;">
      <textarea id="issueDlgText" style="width:100%;height:360px;border:1px solid #e5e7eb;border-radius:6px;padding:8px;resize:vertical;font-size:12px;line-height:1.5;"></textarea>
      <div style="margin-top:10px;display:flex;gap:8px;justify-content:flex-end;">
        <button class="btn" id="issueDlgCopy">复制内容</button>
        <button class="btn primary" id="issueDlgCloseBtn">完成</button>
      </div>
    </div>
  `;
  overlay.appendChild(panel);
  document.body.appendChild(overlay);
  document.getElementById('issueDlgText').value = content || '';
  const close = () => document.body.removeChild(overlay);
  document.getElementById('issueDlgClose').onclick = close;
  document.getElementById('issueDlgCloseBtn').onclick = close;
  document.getElementById('issueDlgCopy').onclick = async () => {
    try {
      await navigator.clipboard.writeText(document.getElementById('issueDlgText').value || '');
      frappe && frappe.msgprint ? frappe.msgprint('已复制到剪贴板') : alert('已复制到剪贴板');
    } catch (e) {
      alert('复制失败，请手动复制');
    }
  };
}

function openIssueCreator(row, detail, trackData) {
  const receiver = detail.receiver || detail.receipt || {};
  const sender = detail.sender || {};
  const lines = [];
  lines.push('【基本信息】');
  lines.push(`公司: ${row.company || ''}`);
  lines.push(`ERP订单: ${row.erpOrder || ''}`);
  lines.push(`运单号: ${row.deliveryOrderNo || ''}`);
  lines.push(`客户单号: ${row.customerOrderNo || ''}`);
  lines.push(`跟踪号: ${row.trackingNumber || ''}`);
  lines.push(`产品/渠道: ${row.productName || ''}`);
  lines.push(`状态: ${mapStatus(row.statusText)}`);
  lines.push('');
  lines.push('【计费信息】');
  const packages = Array.isArray(detail.packages) ? detail.packages : [];
  const totalWeight = packages.reduce((sum, p) => sum + (parseFloat(p.weight) || 0), 0);
  const weightUnit = detail.weight_unit || '';
  const sizeUnit = detail.size_unit || '';
  const chargeWeight = (detail.chargeWeight ?? detail.charge_weight) || '';
  lines.push(`件数: ${detail.pieces ?? (packages.length || '')}`);
  lines.push(`实重: ${totalWeight || detail.weight || ''}${weightUnit ? ' ' + weightUnit : ''}`);
  lines.push(`计费重: ${chargeWeight}`);
  lines.push(`重量单位: ${weightUnit}`);
  lines.push(`尺寸单位: ${sizeUnit}`);
  lines.push('');
  lines.push('【收件人信息】');
  lines.push(`姓名: ${receiver.first_name || receiver.name || ''}`);
  lines.push(`电话: ${receiver.phone_number || row.receiver_phone || ''}`);
  lines.push(`国家/省州/城市: ${receiver.country_code || row.dest || ''} / ${receiver.province || ''} / ${receiver.city || ''}`);
  lines.push(`地址: ${(receiver.address_lines || []).filter(Boolean).join(' ') || row.receiver_address || ''}`);
  lines.push('');
  lines.push('【发件人信息】');
  lines.push(`姓名: ${(sender.first_name || '') + ' ' + (sender.last_name || '')}`);
  lines.push(`电话: ${sender.phone_number || ''}`);
  lines.push(`地址: ${(sender.address_lines || []).filter(Boolean).join(' ') || ''}`);
  lines.push('');
  if (packages.length) {
    lines.push('【包裹列表】');
    packages.forEach((p, idx) => {
      lines.push(`包裹${idx + 1}: 重量=${p.weight ?? ''}${weightUnit ? ' ' + weightUnit : ''} 长=${p.length ?? ''} 宽=${p.width ?? ''} 高=${p.height ?? ''}`);
    });
    lines.push('');
  }
  const decls = Array.isArray(detail.declaration_info) ? detail.declaration_info : [];
  if (decls.length) {
    lines.push('【申报信息】');
    decls.forEach((d, idx) => {
      lines.push(`申报${idx + 1}: SKU=${d.sku_code ?? ''} 中文品名=${d.name_local ?? ''} 英文品名=${d.name_en ?? ''} 数量=${d.quantity ?? ''} 单价=${d.unit_price ?? ''} 申报重=${d.unit_weight ?? ''} 币种=${d.currency ?? ''} HS=${d.hs_code ?? ''}`);
    });
    lines.push('');
  }
  lines.push('【轨迹时间线】');
  const hist = (trackData.tracking_history || []).slice();
  hist.forEach(ev => lines.push(`${fmt(ev.timestamp)}  ${ev.description || ev.event || ''}`));
  const defaultDesc = lines.join('\n');
  const defaultSubject = `[${row.company || '公司'}] 物流问题 - ${row.deliveryOrderNo || ''}`;
  const defaultCompany = row.company || '';

// 费用试算功能已移除
  const overlay = document.createElement('div');
  overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:1200;display:flex;align-items:center;justify-content:center;';
  const panel = document.createElement('div');
  panel.style.cssText = 'width:960px;max-width:95vw;background:#fff;border-radius:8px;box-shadow:0 10px 30px rgba(0,0,0,.2);display:flex;flex-direction:column;';
  panel.innerHTML = `
    <div style="padding:10px 12px;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center;">
      <div style="font-weight:600;">创建问题</div>
      <span style="cursor:pointer;color:#666;" id="issueCreatorClose">关闭</span>
    </div>
    <div style="padding:14px;display:flex;flex-direction:column;gap:10px;">
      <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
        <div>
          <div class="muted" style="margin-bottom:4px;">主题</div>
          <input id="icSubject" type="text" style="width:100%;height:34px;border:1px solid #e5e7eb;border-radius:6px;padding:6px 8px;">
        </div>
        <div>
          <div class="muted" style="margin-bottom:4px;">公司</div>
          <input id="icCompany" type="text" disabled readonly style="width:100%;height:34px;border:1px solid #e5e7eb;border-radius:6px;padding:6px 8px;background:#f8f9fa;">
        </div>
        <div>
          <div class="muted" style="margin-bottom:4px;">订单</div>
          <input id="icOrder" type="text" disabled readonly style="width:100%;height:34px;border:1px solid #e5e7eb;border-radius:6px;padding:6px 8px;background:#f8f9fa;">
        </div>
        <div></div>
      </div>
      <div>
        <div class="muted" style="margin-bottom:4px;">描述</div>
        <textarea id="icDesc" style="width:100%;height:360px;border:1px solid #e5e7eb;border-radius:6px;padding:8px;resize:vertical;font-size:12px;line-height:1.6;white-space:pre-wrap;"></textarea>
      </div>
      <div style="display:flex;gap:8px;justify-content:flex-end;">
        <button class="btn primary" id="icSave">创建</button>
      </div>
    </div>
  `;
  overlay.appendChild(panel);
  document.body.appendChild(overlay);
  document.getElementById('icSubject').value = defaultSubject;
  document.getElementById('icCompany').value = defaultCompany;
  document.getElementById('icOrder').value = row.erpOrder || '';
  document.getElementById('icDesc').value = defaultDesc;
  // 附件选择
  const fileWrap = document.createElement('div');
  fileWrap.innerHTML = `
    <div class="muted" style="margin:8px 0 4px;">附件</div>
    <input id="icFiles" type="file" multiple>
  `;
  panel.querySelector('div[style*="padding:14px"]').appendChild(fileWrap);
  const fileInput = fileWrap.querySelector('#icFiles');
  const close = () => document.body.removeChild(overlay);
  document.getElementById('issueCreatorClose').onclick = close;

  document.getElementById('icSave').onclick = async () => {
    const subject = document.getElementById('icSubject').value || '';
    const company = defaultCompany || '';
    const description = document.getElementById('icDesc').value || '';
    try {
      const res = await frappe.call({
        method: 'frappe.client.insert',
        args: {
          doc: {
            doctype: 'Issue',
            subject: subject,
            status: 'Open',
            description: description,
            company: company,
            custom_orders: row.erpOrder || ''
          }
        }
      });
      const doc = res && res.message;
      if (doc && doc.name) {
        // 附件上传
        if (fileInput && fileInput.files && fileInput.files.length) {
          const fileArr = await Promise.all(Array.from(fileInput.files).map(f => toBase64File(f)));
          await frappe.call({
            method: 'upsystem.upsystem.api_traking.upload_issue_attachments',
            args: { issue_name: doc.name, files: JSON.stringify(fileArr) }
          });
        }
        // 回写 Logistics Order Inst 的 Issue 关联信息
        if (row.erpOrder) {
          try {
            await frappe.call({
              method: 'upsystem.upsystem.api_traking.set_logistics_issue_for_order',
              args: {
                sales_order: row.erpOrder,
                issue_name: doc.name
              }
            });
          } catch (e) {
            console.warn('set_logistics_issue_for_order failed', e);
          }
        }
        // 已通过 custom_orders 建立 Issue -> Sales Order 关联
        const link = `/app/issue/${encodeURIComponent(doc.name)}`;
        frappe && frappe.msgprint ? frappe.msgprint(`已创建 Issue：<a href="${link}" target="_blank">${doc.name}</a>`) : alert(`已创建 Issue: ${doc.name}`);
        close();
        // 刷新列表中的关联问题
        refresh();
      } else {
        frappe && frappe.msgprint ? frappe.msgprint('创建失败，请稍后再试') : alert('创建失败，请稍后再试');
      }
    } catch (e) {
      console.error(e);
      frappe && frappe.msgprint ? frappe.msgprint('创建失败：' + (e.message || '未知错误')) : alert('创建失败：' + (e.message || '未知错误'));
    }
  };
}

function toBase64File(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      const res = (reader.result || '').toString();
      const idx = res.indexOf('base64,');
      const base64 = idx >= 0 ? res.slice(idx + 7) : res;
      resolve({ filename: file.name, data: base64 });
    };
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
}
function handleHoldClick() {
  const checked = Array.from(document.querySelectorAll('#omsTBody input[type="checkbox"]:checked'));
  if (checked.length === 0) {
    frappe.msgprint('请先选择一条订单');
    return;
  }
  if (checked.length > 1) {
    frappe.msgprint('一次仅支持对一条订单执行拦截，请只勾选一条');
    return;
  }
  const idx = parseInt(checked[0].dataset.index || '-1', 10);
  if (isNaN(idx) || idx < 0) {
    frappe.msgprint('无法定位所选订单，请刷新后重试');
    return;
  }
  const row = rendered[idx];
  const wb = row.waybill_number || row.deliveryOrderNo || '';
  if (!wb) {
    frappe.msgprint('未获取到运单号（waybill_number），无法执行拦截');
    return;
  }
  const doHold = async (remark) => {
    try {
      const res = await frappe.call({
        method: 'upsystem.upsystem.api.intercept_order',
        args: { intercept_data: { waybill_number: wb, remark: remark || '客户暂扣' } }
      });
      const msg = (res.message && (res.message.message || res.message.msg)) || '已请求拦截';
      frappe.msgprint(msg);
    } catch (e) {
      console.error(e);
      frappe.msgprint('拦截请求失败');
    }
  };
  // 优先使用 frappe.prompt 获取备注
  const canUseFrappePrompt = !!(window.frappe && frappe.prompt && frappe.ui && frappe.ui.form && typeof frappe.ui.form.make_control === 'function');
  if (canUseFrappePrompt) {
    try {
      frappe.prompt([
        { fieldname: 'remark', label: '拦截备注', fieldtype: 'Small Text', reqd: 1, default: '客户暂扣' }
      ], (values) => doHold(values.remark), '拦截运单', '确定');
    } catch (e) {
      const remark = window.prompt('请输入拦截备注（1-100个字符）', '客户暂扣');
      if (remark) doHold(remark);
    }
  } else {
    const remark = window.prompt('请输入拦截备注（1-100个字符）', '客户暂扣');
    if (remark) doHold(remark);
  }
}

function showDetailDrawer(baseRow, detail, trackData) {
  // 友好状态映射（依据云途文档常见代号，未知则回退原值）
  const mapOrderStatusCode = (code) => {
    const k = String(code || '').toUpperCase();
    const m = {
      S: '已提交',
      D: '草稿',
      F: '已出仓',
      T: '运输中',
      E: '异常',
      R: '退回',
      C: '已取消',
      P: '处理中',
      DELIVERED: '已签收'
    };
    return m[k] || k || '-';
  };

  // 组装字段（尽量把云途返回的细节都展示出来）
  const receiver = detail.receiver || detail.receipt || {};
  const sender = detail.sender || {};

  const basic = {
    运单号: detail.waybill_number || baseRow.deliveryOrderNo || '',
    客户单号: detail.customer_order_number || baseRow.customerOrderNo || '',
    物流产品编码: detail.product_code || baseRow.productName || '',
    跟踪号: detail.tracking_number || baseRow.trackingNumber || '',
    平台账号: detail.platform_account_code || '',
    件数: detail.pieces ?? '',
    重量单位: detail.weight_unit || '',
    尺寸单位: detail.size_unit || '',
    订单状态: `${mapOrderStatusCode(detail.status)}${detail.status ? ` (${detail.status})` : ''}` || '',
    走货属性: detail.sensitive_type ?? '',
    来源: detail.source_code || '',
    计费重: detail.chargeWeight ?? detail.charge_weight ?? '',
    是否冻结金额: detail.is_freeze_amount === true ? '是' : (detail.is_freeze_amount === false ? '否' : ''),
  };

  const recv = {
    名称: [receiver.first_name, receiver.last_name].filter(Boolean).join(' ').trim() || '',
    公司: receiver.company || '',
    国家代码: receiver.country_code || baseRow.dest || '',
    省州: receiver.province || receiver.state || '',
    城市: receiver.city || '',
    邮编: receiver.postal_code || '',
    电话: receiver.phone_number || receiver.phone || receiver.mobile || '',
    邮箱: receiver.email || '',
    地址行: (receiver.address_lines || []).filter(Boolean).join(' | ')
  };

  const send = {
    名称: [sender.first_name, sender.last_name].filter(Boolean).join(' ').trim() || '',
    公司: sender.company || '',
    国家代码: sender.country_code || '',
    省州: sender.province || sender.state || '',
    城市: sender.city || '',
    邮编: sender.postal_code || '',
    电话: sender.phone_number || sender.phone || '',
    邮箱: sender.email || '',
    地址行: (sender.address_lines || []).filter(Boolean).join(' | ')
  };

  // 包裹信息与申报信息
  const packages = Array.isArray(detail.packages) ? detail.packages : [];
  const decls = Array.isArray(detail.declaration_info) ? detail.declaration_info : [];

  // 平台/计费信息（就近放置）
  const platform = {
    平台账号: detail.platform_account_code || '',
    来源: detail.source_code || '',
  };

  const totalWeight = packages.reduce((sum, p) => sum + (parseFloat(p.weight) || 0), 0);
  const fees = {
    件数: detail.pieces ?? (packages.length || ''),
    实重: totalWeight ? `${totalWeight}${detail.weight_unit ? ' ' + detail.weight_unit : ''}` : (detail.weight ? `${detail.weight}${detail.weight_unit ? ' ' + detail.weight_unit : ''}` : ''),
    计费重: (detail.chargeWeight ?? detail.charge_weight) || '',
    重量单位: detail.weight_unit || '',
    尺寸单位: detail.size_unit || '',
  };

  const timeline = (trackData.tracking_history || []).slice().map(ev => ({
    time: ev.timestamp,
    utc: ev.utc_time || '',
    text: ev.description || ev.event || '',
    node: ev.track_node_code || ev.node_code || '',
    location: ev.location || '',
    pod_url: ev.pod_url || ''
  }));

  // 辅助渲染数组表格
  const renderArrayTable = (title, items, columns) => {
    if (!Array.isArray(items) || items.length === 0) return '';
    const header = `<tr>${columns.map(c => `<th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">${c.label}</th>`).join('')}</tr>`;
    const body = items.map(it => `<tr>${columns.map(c => {
      const v = typeof c.get === 'function' ? c.get(it) : (it[c.key] ?? '');
      return `<td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;white-space:nowrap;">${v === null || v === undefined || v === '' ? '-' : v}</td>`;
    }).join('')}</tr>`).join('');
    return `<div style="margin:10px 0 18px 0;">
      <div style="font-weight:600;margin-bottom:6px;">${title}</div>
      <div style="overflow:auto;">
        <table style="border-collapse:collapse;min-width:600px;">${header}${body}</table>
      </div>
    </div>`;
  };

  const packagesHtml = renderArrayTable('包裹信息', packages, [
    { key: 'weight', label: '重量' },
    { key: 'length', label: '长' },
    { key: 'width', label: '宽' },
    { key: 'height', label: '高' },
  ]);

  const declsHtml = renderArrayTable('申报信息', decls, [
    { key: 'sku_code', label: 'SKU' },
    { key: 'name_local', label: '中文品名' },
    { key: 'name_en', label: '英文品名' },
    { key: 'quantity', label: '数量' },
    { key: 'unit_price', label: '单价' },
    { key: 'unit_weight', label: '申报重' },
    { key: 'currency', label: '币种' },
    { key: 'hs_code', label: 'HS编码' },
    { key: 'brand', label: '品牌' },
    { key: 'spec', label: '规格' },
    { key: 'model', label: '型号' },
    { key: 'material', label: '材质' },
    { key: 'purpose', label: '用途' },
    { key: 'remark', label: '备注' },
    { key: 'sales_url', label: '链接' },
  ]);

  const rawJsonHtml = `<pre style="background:#f8f9fa;border:1px solid #eee;padding:10px;white-space:pre-wrap;word-break:break-all;border-radius:6px;">${(function(){
    try { return JSON.stringify(detail, null, 2); } catch(e) { return ''; }
  })()}</pre>`;

  // 日志信息（如果返回里带有则展示）
  const logs = detail.logs || detail.log_info || detail.operation_logs || detail.operate_logs || [];
  const renderLogs = (items) => {
    if (!Array.isArray(items) || items.length === 0) return '';
    const cols = [
      { label: '操作时间', get: (x) => x.time || x.operate_time || x.operation_time || x.timestamp || '-' },
      { label: '操作人', get: (x) => x.operator || x.user || x.operator_name || '-' },
      { label: '内容', get: (x) => x.content || x.message || x.remark || x.desc || '-' },
    ];
    return renderArrayTable('日志信息', items, cols);
  };

  // 构建抽屉
  const overlay = document.createElement('div');
  overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:1000;';
  const drawer = document.createElement('div');
  drawer.style.cssText = 'position:absolute;top:0;right:0;width:75vw;max-width:1600px;height:100%;background:#fff;overflow:auto;box-shadow:-2px 0 10px rgba(0,0,0,.15);padding:16px 20px;';
  overlay.appendChild(drawer);
  drawer.innerHTML = `
    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
      <div style="font-weight:600;font-size:16px;">订单详情</div>
      <span style="cursor:pointer;color:#666;" id="omsDetailClose">关闭</span>
    </div>
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
      ${renderSection('基本信息', basic)}
      ${renderTimeline(timeline)}
      ${renderSection('计费信息', fees)}
      ${renderSection('收件人信息', recv)}
      <div style="grid-column:1 / -1;">${packagesHtml}</div>
      <div style="grid-column:1 / -1;">${declsHtml}</div>
      <div style="grid-column:1 / -1;">${renderLogs(logs)}</div>
    </div>
  `;
  document.body.appendChild(overlay);
  document.getElementById('omsDetailClose').onclick = () => document.body.removeChild(overlay);
}

function renderSection(title, obj) {
  const rows = Object.keys(obj).map(k => {
    return `<div style="display:flex;padding:6px 0;border-bottom:1px dashed #f1f3f5;">
      <div style="width:110px;color:#6c757d;">${k}</div>
      <div style="flex:1;">${obj[k] || '-'}</div>
    </div>`;
  }).join('');
  return `<div style="margin:10px 0 18px 0;">
      <div style="font-weight:600;margin-bottom:6px;">${title}</div>
      <div>${rows}</div>
    </div>`;
}

function renderTimeline(items) {
  if (!items || !items.length) return '';
  const header = `<tr>
    <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">时间</th>
    <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">UTC时间</th>
    <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">节点代码</th>
    <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">节点名称(中文)</th>
    <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">节点名称(英文)</th>
    <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">节点状态</th>
    <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">内容</th>
    <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">地点</th>
    <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #eee;">POD</th>
  </tr>`;
  const body = items.map(it => {
    const m = getNodeMeta(it.node);
    const pod = it.pod_url ? `<a href="${it.pod_url}" target="_blank">查看</a>` : '-';
    return `<tr>
      <td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;">${fmt(it.time)}</td>
      <td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;">${fmt(it.utc)}</td>
      <td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;">${it.node || '-'}</td>
      <td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;">${m.zh || '-'}</td>
      <td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;">${m.en || '-'}</td>
      <td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;">${m.cat || '-'}</td>
      <td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;">${it.text || '-'}</td>
      <td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;">${it.location || '-'}</td>
      <td style="padding:6px 8px;border-bottom:1px dashed #f1f3f5;">${pod}</td>
    </tr>`;
  }).join('');
  return `<div style="margin:10px 0 18px 0;">
    <div style="font-weight:600;margin-bottom:6px;">轨迹信息</div>
    <div style="overflow:auto;">
      <table style="border-collapse:collapse;min-width:960px;">${header}${body}</table>
    </div>
  </div>`;
}

async function enrichOrderDetailsSequential() {
  for (let i = 0; i < rendered.length; i++) {
    const r = rendered[i];
    if (!r || !r.deliveryOrderNo) continue;
    try {
      const od = await frappe.call({
        method: 'upsystem.upsystem.api.get_order_detail',
        args: { query_data: { order_number: r.deliveryOrderNo, waybill_number: r.deliveryOrderNo } }
      });
      const d = (od.message && od.message.data) || {};
      // 兼容字段名
      const receiver = d.receiver || d.receipt || {};
      r.receiver_name = receiver.name || r.receiver_name;
      r.receiver_phone = receiver.phone || receiver.mobile || r.receiver_phone;
      r.receiver_state = receiver.state || receiver.province || r.receiver_state;
      const addrParts = [receiver.country, receiver.state || receiver.province, receiver.city, receiver.address, receiver.street, receiver.postal_code].filter(Boolean);
      r.receiver_address = addrParts.join(' ');
      r.company_name = d.company_name || r.company_name;
      r.order_status = d.status || r.order_status;
      r.eu_vat = d.eu_vat || d.vat_number || r.eu_vat;
      r.ioss = d.ioss || d.ioss_number || r.ioss;
      r.house_number = receiver.house_number || r.house_number;
      r.weight = r.weight || d.weight || '';
      r.charge_weight = d.charge_weight || r.charge_weight;
      r.outer_pkg_count = d.outer_package_count || r.outer_pkg_count;
      r.proof_url = d.proof_url || r.proof_url;
      // 局部刷新行
      const tbody = document.getElementById('omsTBody');
      if (tbody && tbody.rows[i]) {
        const cells = tbody.rows[i].cells;
        // 根据最新列序刷新
        cells[6].textContent = r.receiver_name || '-';          // 收件人姓名
        cells[7].textContent = r.receiver_phone || '-';         // 电话
        // cells[8] 为状态，不在此处直接改
        cells[9].textContent = r.company_name || '-';           // 公司名称
        cells[10].textContent = r.receiver_state || '-';        // 收件人州省
        cells[11].textContent = r.receiver_address || '-';      // 收件人地址
        cells[12].textContent = r.order_status || '-';          // 订单状态
        cells[13].textContent = r.weight || '-';                // 包裹重量
        cells[14].textContent = r.charge_weight || '-';         // 计费重
        cells[15].textContent = r.outer_pkg_count || '-';       // 外包裹数量
    // 时间列 16~19 + 最新事件列 20 为渲染自轨迹，不改
    cells[21].textContent = r.eu_vat || '-';                // 欧盟税号
    cells[22].textContent = r.ioss || '-';                  // IOSS识别码
    cells[23].textContent = r.house_number || '-';          // 门牌号
    cells[24].innerHTML = r.proof_url ? `<a href="${r.proof_url}" target="_blank">查看</a>` : '-'; // 签收证明
      }
    } catch (e) {
      console.error('order detail err', e);
    }
  }
}

function openIssueEditor(issueName) {
  (async () => {
    try {
      const resp = await frappe.call({ method: 'frappe.client.get', args: { doctype: 'Issue', name: issueName } });
      const doc = resp.message || {};
      const safe = (v) => String(v || '').replace(/"/g, '&quot;');
      const overlay = document.createElement('div');
      overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:1200;display:flex;align-items:center;justify-content:center;';
      const panel = document.createElement('div');
      panel.style.cssText = 'width:820px;max-width:90vw;background:#fff;border-radius:8px;box-shadow:0 10px 30px rgba(0,0,0,.2);display:flex;flex-direction:column;';
      panel.innerHTML = `
        <div style="padding:10px 12px;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center;">
          <div style="font-weight:600;">编辑问题 ${issueName}</div>
          <span style="cursor:pointer;color:#666;" id="ieClose">关闭</span>
        </div>
        <div style="padding:14px;display:flex;flex-direction:column;gap:10px;">
          <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
            <div>
              <div class="muted" style="margin-bottom:4px;">主题</div>
              <input type="text" value="${safe(doc.subject)}" disabled readonly style="width:100%;height:34px;border:1px solid #e5e7eb;border-radius:6px;padding:6px 8px;background:#f8f9fa;">
            </div>
            <div>
              <div class="muted" style="margin-bottom:4px;">公司</div>
              <input type="text" value="${safe(doc.company)}" disabled readonly style="width:100%;height:34px;border:1px solid #e5e7eb;border-radius:6px;padding:6px 8px;background:#f8f9fa;">
            </div>
            <div>
              <div class="muted" style="margin-bottom:4px;">订单</div>
              <input type="text" value="${safe(doc.custom_orders)}" disabled readonly style="width:100%;height:34px;border:1px solid #e5e7eb;border-radius:6px;padding:6px 8px;background:#f8f9fa;">
            </div>
            <div style="display:flex;align-items:flex-end;">
              <div class="muted" style="margin-bottom:4px;">状态</div>
              <div style="margin-left:8px;">${doc.status || '-'}</div>
            </div>
            <div>
              <div class="muted" style="margin-bottom:4px;">创建时间</div>
              <input type="text" value="${safe(fmt(doc.creation) || '')}" disabled readonly style="width:100%;height:34px;border:1px solid #e5e7eb;border-radius:6px;padding:6px 8px;background:#f8f9fa;">
            </div>
          </div>
          <div>
            <div class="muted" style="margin-bottom:4px;">原始描述</div>
            <textarea id="ieDescReadonly" disabled readonly style="width:100%;height:160px;border:1px solid #e5e7eb;border-radius:6px;padding:8px;background:#f8f9fa;resize:vertical;font-size:12px;line-height:1.6;white-space:pre-wrap;"></textarea>
          </div>
          <div>
            <div class="muted" style="margin-bottom:4px;">处理说明</div>
            <textarea id="ieResolution" style="width:100%;height:220px;border:1px solid #e5e7eb;border-radius:6px;padding:8px;resize:vertical;font-size:12px;line-height:1.6;white-space:pre-wrap;">${doc.resolution || doc.resolution_details || ''}</textarea>
          </div>
          <div>
            <div class="muted" style="margin-bottom:4px;">附件</div>
            <input id="ieFiles" type="file" multiple>
          </div>
          <div style="display:flex;gap:8px;justify-content:flex-end;">
            <button class="btn primary" id="ieSave">保存</button>
          </div>
        </div>
      `;
      overlay.appendChild(panel);
      document.body.appendChild(overlay);
      const descEl = panel.querySelector('#ieDescReadonly');
      if (descEl) descEl.value = doc.description || '';
      const close = () => document.body.removeChild(overlay);
      panel.querySelector('#ieClose').onclick = close;
      panel.querySelector('#ieSave').onclick = async () => {
        const reso = panel.querySelector('#ieResolution').value || '';
        try {
          await frappe.call({ method: 'upsystem.upsystem.api_traking.update_issue_resolution', args: { issue_name: issueName, resolution: reso } });
          const files = panel.querySelector('#ieFiles').files || [];
          if (files.length) {
            const fileArr = await Promise.all(Array.from(files).map(f => toBase64File(f)));
            await frappe.call({ method: 'upsystem.upsystem.api_traking.upload_issue_attachments', args: { issue_name: issueName, files: JSON.stringify(fileArr) } });
          }
          frappe.msgprint('已保存');
          close();
          refresh();
        } catch (e) {
          console.error(e);
          frappe.msgprint('保存失败');
        }
      };
    } catch (e) {
      console.error('openIssueEditor failed', e);
    }
  })();
}

function exportCurrent() {
  if (!rendered.length) {
    frappe.msgprint('没有数据可导出');
    return;
  }
  const headers = ['运单号','客户单号','跟踪号','运输方式','收件人国家','收件人姓名','电话','状态','公司名称','收件人州省','收件人地址','订单状态','包裹重量','计费重','外包裹数量','创建时间','收货时间','发货时间','签收时间','最新事件时间','最新事件','欧盟税号','IOSS识别码','门牌号','签收证明'];
  const rows = rendered.map(r => [
    r.deliveryOrderNo || '',
    r.customerOrderNo || '',
    r.trackingNumber || '',
    r.productName || '',
    r.dest || '',
    r.receiver_name || '',
    r.receiver_phone || '',
    mapStatus(r.statusText),
    r.company_name || '',
    r.receiver_state || '',
    r.receiver_address || '',
    r.order_status || '',
    r.weight || '',
    r.charge_weight || '',
    r.outer_pkg_count || '',
    fmt(r.creationTime),
    fmt(r.checkInTime),
    fmt(r.checkOutTime),
    fmt(r.deliveredTime),
    fmt(r.latestTime),
    r.latestEvent || '',
    r.eu_vat || '',
    r.ioss || '',
    r.house_number || '',
    r.proof_url || ''
  ]);
  const csv = [headers, ...rows].map(row => row.map(f => `"${String(f || '').replace(/"/g,'""')}"`).join(',')).join('\n');
  // 解决 Excel 打开 UTF-8 中文乱码：在 CSV 前添加 UTF-8 BOM
  const bom = '\uFEFF';
  const blob = new Blob([bom + csv], {type:'text/csv;charset=utf-8;'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `oms_tracking_${new Date().toISOString().slice(0,10)}.csv`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}
</script>
{% endblock %}

{% block footer %}{% endblock %}


